<?php

class DocFormatter 
{

	var $in_pre;
	var $headings;
	var $list_stack;
	var $link_suffix;

	function DocFormatter ()
	{
		$this->link_suffix = ".html";
		
	}

	function format_file ($file)
	{
		$handle = fopen ($file, "rb");
		if ($handle == false) return false;	

		$this->in_pre = false;
		$this->headings = array ();
		$this->list_stack = array ();

		$text = "";
		while (!feof($handle)) {
			# fgets may not read an entire line 
			# if you php is earlier than 4.3
			$line = fgets ($handle);

			$len = strlen($line);
			if ($len >= 2 && substr($line,$len-2,2) == "\r\n") {
				$line = substr($line, 0, $len-2);
			}
			else if ($len >= 1 && ($line{$len-1} == "\n" || $line{$len-1} == "\r")) {
				$line = substr($line, 0, $len-1);
			}

			$text = $text . $this->format_line ($line);
		}
		fclose ($handle);

		$heading = $this->emit_headings ();

		if ($heading == "") return $text;
		else return "<b><i>Contents</i></b>\n" . $heading . $text;
	}

	function format_page ($page)
	{
		$page_lines = explode ("\n", $page);

		$this->in_pre = false;
		$this->headings = array ();
		$this->list_stack = array ();

		$text = "";
		foreach ($page_lines as $line) {
			$text = $text . $this->format_line ($line);
		}

		$heading = $this->emit_headings ();

		if ($heading == "") return $text;
		else return "<b><i>Contents</i></b>\n" . $heading . $text;
	}

	function get_heading_num ()
	{
		$count = count($this->headings);
		if ($count <= 0) return "";

		$stack = array ();
		$index = 1;
		$num = "";
		for ($i = 0; $i < $count; $i++) {
			$depth = $this->headings[$i][0];

			if ($i == 0 || $depth == $prev_depth) {
				/* don't do anything */
			}
			else if ($depth < $prev_depth) {
				if (count($stack) > 0) {
					$index = array_pop($stack) + 1;
				}
			}
			else {
				array_push ($stack, $index - 1);
				$index = 1;
			}

			if ($i == $count - 1) {
				for ($x = 0; $x < count($stack); $x++) {
					$num = $num . sprintf ("%d", $stack[$x]) . ".";
				}
				$num = $num . sprintf ("%d", $index);
			}

			$index = $index + 1;
			$prev_depth = $depth;
		}

		return $num;
	}

	function emit_headings ()
	{
		$text = "";

		$count = count($this->headings);
		if ($count <= 0) return $text;
		
		$text = $text . "<div><dl><dt>\n";
		$text = $text . "<dl>\n";

		$stack = array ();
		$index = 1;
		for ($i = 0; $i < $count; $i++) {
			$depth = $this->headings[$i][0];
			$title = $this->headings[$i][1];

			if ($i == 0 || $depth == $prev_depth) {
				/* don't do anything */
			}
			else if ($depth < $prev_depth) {
				if (count($stack) > 0) {
					$index = array_pop($stack) + 1;
				}

				$text = $text . "</dl>\n";
			}
			else {
				array_push ($stack, $index - 1);
				$index = 1;

				$text = $text . "<dl>\n";
			}

			$num = "";
			for ($x = 0; $x < count($stack); $x++) {
				$num = $num . sprintf ("%d", $stack[$x]) . ".";
			}
			$num = $num . sprintf ("%d", $index);
			$text = $text . sprintf ("<dt>%s <a href='#toc-%d'>%s</a></dt>\n", $num, $i, $title);

			$index = $index + 1;
			$prev_depth = $depth;
			$prev_title = $title;
		}

		$text = $text . "</dl>\n";
		$text = $text . "</dt></dl></div>\n";

		return $text;
	}

	function clear_list ()
	{
		$text = "";
		while (count($this->list_stack) > 0) {
			$text = $text . "</ol>\n";
			array_pop ($this->list_stack);
		}
		return $text;
	}

	function format_line ($line)
	{

		$tmp = $this->format_pre ($line);
		if ($tmp != false) {
			return $this->clear_list() . $tmp . "\n";
		}

		if ($this->in_pre) {
			# the text in a <pre></pre> block
			return htmlspecialchars($line) . "\n";
		}

		if (trim($line) == "") {
			return $this->clear_list() . "<br>\n";
		}

		$tmp = $this->format_heading ($line);
		if ($tmp != false) {
			return $this->clear_list() . $tmp . "\n";
		}

		$tmp = $this->format_list ($line);
		if ($tmp != false) {
			// TODO: furthur attribute processing... bold, italic
			return $tmp . "\n";
		}

		return $this->clear_list() . $this->format_text($line) . "<br>\n";
	}

	function format_pre ($line)
	{
		# {{{ something }}} 
		$pattern = '/^[[:space:]]*{{{([^{].*[^}]|[^{}])}}}[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			if ($this->in_pre == false) {
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $text, 1);
				return '<pre class=wiki>' . htmlspecialchars($text) . '</pre>';
			}
			else return htmlspecialchars($line);
		} 

		if ($this->in_pre == false) {
			# {{{ something
			$pattern = '/^[[:space:]]*{{{[^{]/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				return '<pre class=wiki>' . htmlspecialchars($text);
			}

			# {{{
			$pattern = '/^[[:space:]]*{{{[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				return '<pre class=wiki>' . $text;
			}
		}
		else {
			# something }}}	
			$pattern = '/[^}]}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $line, 1);
				return htmlspecialchars($text) . '</pre>';
			}

			# }}}
			$pattern = '/^[[:space:]]*}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $line, 1);
				return $text . '</pre>';
			}
		}

		return false;
	}

	function format_heading ($line)
	{
		# ===== heading =====
		$pattern = '/^[[:space:]]*=====([^=].*[^=]|[^=])=====[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*=====/', '', $line, 1);
			$text = preg_replace ('/=====[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(5, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h5 class=wiki><a name='#toc-%d'>%s %s</a></h5>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# ==== heading ====
		$pattern = '/^[[:space:]]*====([^=].*[^=]|[^=])====[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*====/', '', $line, 1);
			$text = preg_replace ('/====[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(4, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h4 class=wiki><a name='#toc-%d'>%s %s</a></h4>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# === heading ===
		$pattern = '/^[[:space:]]*===([^=].*[^=]|[^=])===[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*===/', '', $line, 1);
			$text = preg_replace ('/===[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(3, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h3 class=wiki><a name='#toc-%d'>%s %s</a></h3>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# == heading ==
		$pattern = '/^[[:space:]]*==([^=].*[^=]|[^=])==[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*==/', '', $line, 1);
			$text = preg_replace ('/==[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(2, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h2 class=wiki><a name='#toc-%d'>%s %s</a></h2>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# = heading =
		$pattern = '/^[[:space:]]*=([^=].*[^=]|[^=])=[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*=/', '', $line, 1);
			$text = preg_replace ('/=[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(1, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h1 class=wiki><a name='#toc-%d'>%s %s</a></h1>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		return false;
	}

	function format_list ($line)
	{
		# 1. something
		$pattern = '/^[[:space:]]+[[:digit:]]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[[:digit:]]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);
			
			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1] < $nspaces) {
				$text = "<ol type='1'>\n" . $text;
				array_push ($this->list_stack, $nspaces);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}
			return $text;
		}

		# a. something
		$pattern = '/^[[:space:]]+[a-z]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[a-z]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);

			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1] < $nspaces) {
				$text = "<ol type='a'>\n" . $text;
				array_push ($this->list_stack, $nspaces);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}

			return $text;
		}

		# A. something
		$pattern = '/^[[:space:]]+[A-Z]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[A-Z]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);

			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1] < $nspaces) {
				$text = "<ol type='A'>\n" . $text;
				array_push ($this->list_stack, $nspaces);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}

			return $text;
		}

		return false;
	}

	function format_text ($line)
	{
		$text = htmlspecialchars($line);	

		/*
		// TODO: remember macro locations and replace them by actual values later...
		# [[[macro]]]
		$pattern = '/\[\[\[[:alnum:]+\]\]\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			$x = sprintf ("<a href='%s'>%s</a>", $link, $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}
		*/

		# [[document name]]
		$pattern = '/\[\[[[:alnum:][:space:]~!@#$%^&*()-+-={}|<>?,.;:]+\]\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			$x = sprintf ("<a href='%s'>%s</a>", $link, $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# [document]
		$pattern = '/\[[[:alnum:]~!@#$%^&*()_+-={}|<>?,.;:]+\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 1, strlen($link) - 2);

			list($cap, $url) = split (",", $link, 2);
			if ($url == "") {
				$x = sprintf ("<a href='%s%s'>%s</a>", 
					$link, $this->link_suffix, $link);
			}
			else {
				$x = sprintf ("<a href='%s%s'>%s</a>", 	
					$url, $this->link_suffix, $cap);
			}
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# '''''something'''''
		$pattern = "/'''''([^'][^[:space:]]*[^']|[^'])'''''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 5, strlen($link) - 10);

			$x = sprintf ("<b><i>%s</i></b>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# '''something'''
		$pattern = "/'''([^'][^[:space:]]*[^']|[^'])'''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 3, strlen($link) - 6);

			$x = sprintf ("<b>%s</b>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# ''something''
		$pattern = "/''([^'][^[:space:]]*[^']|[^'])''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			$x = sprintf ("<i>%s</i>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		return $text;
	}

}

?>

